package waal.pc.backend.channels;

import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Queue;
import java.util.Set;
import java.util.concurrent.ConcurrentLinkedQueue;

import waal.pc.backend.dataPoints.DataPoint;
import waal.pc.backend.module.Module;

public abstract class Channel implements Comparable<Channel> {

	Module module;
	int channelNumber, id;
	String name;
	boolean input, digital;
	
	protected Map<ChannelDataListener, Queue<DataPoint>> data = new HashMap<ChannelDataListener,Queue<DataPoint>>();
	protected Set<ChannelChangeListener> ccListener = new HashSet<ChannelChangeListener>();
	
	/**
	 * Create a new channel
	 * @param hub The Hub for this channel
	 * @param number The channel number
	 * @param unit Unitstring
	 */
	public Channel(Module module, int number, String name, boolean input, boolean digital) {
		this.module = module;
		this.channelNumber = number;
		this.name = name;
		this.id = (module.getID() * 10) + number;
		this.input = input;
		this.digital = digital;
	}
	
	/**
	 * Get the Channel Resolution
	 */
	public int getBits() {
		return this.module.getBits();
	}
	
	/**
	 * Get the Channel Number
	 * @return Number of the channel
	 */
	public int getChannelNumber() {
		return this.channelNumber;
	}
	
	public int getID() {
		return this.id;
	}
	
	/**
	 * Get the upper Bound of the channel
	 * @return
	 */
	public double getMax() {
		return this.module.getMax();
	}
	
	/**
	 * Get the lower Bound of the channel 
	 * @return
	 */
	public double getMin() {
		return this.module.getMin();
	}
		
	/**
	 * Get the Hub Name for this Channel
	 * @return String with the name of the Hub
	 */
	public String getModuleName() {
		return this.module.getName();
	}
	
	public Module getModule() {
		return this.module;
		
	}	
	
	/**
	 * Gets the Unit 
	 * @return UnitString
	 */
	public String getUnit() {
		return this.module.getUnit();
	}

	public boolean isActive() {
		return this.module.isChannelActive(this.channelNumber);
	}
	
	public boolean isDigital() {
		return this.digital;
	}
	
	public boolean isInput() {
		return this.input;
	}
	
	public void setActive(boolean active) {
		if(active) {
			this.module.SendConfig(this.channelNumber, 0xFF, 1);
		} else {
			this.module.SendConfig(this.channelNumber, 0xFF, 0);
		}
	}

	public void setName(String name) {
		this.name = name;
	}
	
	public String toString() {
		return this.name;
	}
		
	public int compareTo(Channel c) {
		if(c.getID() > this.id) {
			return -1;
		} else if(c.getID() < this.id) {
			return 1;
		} else {
			return 0;
		}
	}
	
	public void addChannelDataListener(ChannelDataListener cl) {
		if(!this.data.containsKey(cl)) 
			this.data.put(cl, new ConcurrentLinkedQueue<DataPoint>());
	}
	
	public boolean removeChannelDataListener(ChannelDataListener cl) {
		if(this.data.containsKey(cl)) {
			this.data.remove(cl);
			return true;
		} else {
			return false;
		}
	}
	
	/**
	 * Get the Queue for the Listener
	 * @param cl the ChannelListener
	 * @return The Queue for the Listener or null if the Listener wasn't registered as Listener
	 */
	public Queue<DataPoint> getMyQueue(ChannelDataListener cl) {
		if(this.data.containsKey(cl)) { // listener exists
			//System.out.println("want que and are listener(" + cl.getClass().getName() + ") now channel: " + this.channelNumber + " has " + this.data.size() + " listener");
			return this.data.get(cl);
		} else {
			
			Queue<DataPoint> q = new ConcurrentLinkedQueue<DataPoint>();
			this.data.put(cl, q);
			//System.out.println("want que but not listener(" + cl.getClass().getName() + ") now channel: " + this.channelNumber + " has " + this.data.size() + " listener");
			return q;
		}
	}
	
	public boolean addListener(ChannelChangeListener cl) {
		return this.ccListener.add(cl);
	}
	
	public boolean removeListener(ChannelChangeListener cl) {
		return this.ccListener.remove(cl);
	}
	
	public void fireChannelChange() {
		for(Iterator<ChannelChangeListener> it = this.ccListener.iterator(); it.hasNext();  ) {
			it.next().updateFormChannel(this.channelNumber, this);
		}
	}
	
	public void addDataForChannelDataListener(DataPoint p) {
		//System.out.println("add data for Channel " + this.channelNumber + " for listeners: " + this.data.size());
		for(Iterator<Queue<DataPoint>> i = this.data.values().iterator(); i.hasNext();) {
			i.next().offer(p);
		}
	}
	
	/** 
	 * Inform all Listener of the channel, that the channel goes down
	 * @param type 1->Channel goes down; 2->Channel gets inactive; 3-> channel gets active  
	 */
	public void CloseChannel() {
		for(Iterator<ChannelDataListener> i = this.data.keySet().iterator(); i.hasNext(); ) {
			i.next().channelGetKilled(this);
		}
		for(Iterator<ChannelChangeListener> i = this.ccListener.iterator(); i.hasNext(); ) {
			i.next().channelGetKilled(this);
		}	
	}

	public abstract double getLastValue();
	

}
